﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using VDataAdapter.BlockingQueue;
using log4net;
using System.Threading;
using VDataAdapter.PriceServerAdapter;
using VDataAdapter.WorkerManagement;
using System.Collections;
using VDataAdapter.QueueManagement;
using VDataAdapter.Communication;
using VDataAdapter.ClientServer;

namespace VDataAdapter.ClientManagement
{
    class Client
    {
        protected readonly ILog _log = LogManager.GetLogger(typeof(Client));

        private ClientManager _manager;

        private string _parentName;

        private TcpClient _clientSocket;
        private ArrayList _memHistories;
        private VDataCommunicator _clientComm;
        private CommunicationDataQueue _clientDataQueue;
        private SimpleEventQueue _clientEventQueue;
        private IPSEventQueueEnqueuerFactory _clientPSEventHandlerFactory;
        private VDataClientThread _clientServerThread;
        private WorkerManager _clientWorkerManager;
        private PSEventQueueEnqueuer _clientPSEventHandler;

        private bool _polling;

        private string _name;
        public string Name
        {
            get { return _name; }
        }

        public Client(ClientManager manager, string parentName, TcpClient clientSocket, IWorkerScheduler workerScheduler, IPSEventQueueEnqueuerFactory psEventHandlerFactory)
        {
            _name = clientSocket.Client.RemoteEndPoint.ToString();

            _manager = manager;

            _parentName = parentName;

            //Store client socket
            _clientSocket = clientSocket;

            //Prepare connector and queue that will connect all components
            _memHistories = new ArrayList();
            _clientDataQueue = new CommunicationDataQueue();
            DataSource dataSource = new DataSource();
            _clientComm = new VDataCommunicator(dataSource, _clientDataQueue);
            _clientEventQueue = new SimpleEventQueue();

            //Create all thread that start the work
            //Java server thread
            _clientServerThread = new VDataClientThread(_clientSocket, _clientDataQueue, _memHistories);
            _clientServerThread.acceptResetHandler(handleJavaServerReset);

            //Worker threads
            _clientWorkerManager = new WorkerManager(workerScheduler, handleWorkerException);


            //PS listener thread
            _clientPSEventHandlerFactory = psEventHandlerFactory;
            _clientPSEventHandler = _clientPSEventHandlerFactory.createPSEventHandler(_clientEventQueue, handlePriceServerReset);

            _polling = true;
        }

        //start serving client
        public void startServingClient()
        {
            Thread thread = new Thread(new ThreadStart(start));
            thread.Name = _name;
            _log.Info("Start Client thread: " + thread.Name);
            thread.Start();
        }

        private void start()
        {
            _log.Info("Start all worker thread");

            //Start the client server thread to pick data from data queue and pass to VData java server
            Thread clientServerThread = StartNewQueueSender();

            _log.Debug("Start all workers");
            //Start all the worker that listen to event queues and do the actual work of converting event to message
            _clientWorkerManager.StartAllWorkers(_clientEventQueue, _clientComm);

            //Start event handler to listen to price server to update event queue
            Thread pSListenerThread = StartPriceServerListener();

            _log.Info("All worker thread stared");

            //_log.Info("Start checking incorrect sequence");

            // polling for reload data seq = 1
            //while (_polling)
            //{
            //       //Sleep for a while
            //       Thread.Sleep(200);

            //       //Check if VdataClient request reload or not
            //       if (_clientServerThread.IsReloadData)
            //       {
            //            _polling = false;
            //            _log.Info(">> Reset Price Server service data because seq = 1");
            //            handleIncorrectSequence();
            //      }
            //}

            //_log.Info("Stop checking incorrect sequence");

            /*while(_polling){
                Thread.Sleep(1000);
            }
            */
            
            clientServerThread.Join();
            pSListenerThread.Join();
        }

        protected Thread StartNewQueueSender()
        {
            string parentName = Thread.CurrentThread.Name;
            Thread thread = new Thread(new ThreadStart(_clientServerThread.Start));
            thread.Name = parentName + "-ClientQueueServer";
            _log.Debug("Start ClientQueueServer");
            thread.Start();
            return thread;
        }

        protected Thread StartPriceServerListener()
        {
            string parentName = Thread.CurrentThread.Name;
            Thread thread = new Thread(new ThreadStart(_clientPSEventHandler.StartListening));
            thread.Name = parentName + "-ClientPSListener";
            _log.Debug("Start ClientPSListener");
            thread.Start();
            return thread;
        }


        //Stop serving the client, close the connection
        public void stopServingClient()
        {
            try
            {
                //Stop checking thread status
                _polling = false;

                //Stop all worker
                _clientWorkerManager.StopAllWorkers();

                //Unregister to Price Server
                _clientPSEventHandler.StopListening();

                //Close client connection
                _clientSocket.Close();
            }
            catch (Exception ex)
            {
                string msg = "Error closing connection";
                _log.Error(msg, ex);
            }
        }

        public void handlePriceServerReset(Exception ex)
        {
            _log.Error("PS Listener throw exception:", ex);
            _log.Info("Reset all client");
            _manager.handlePriceServerReset(this);
        }

        //Reset PriceServerEventHandler
        private void handleIncorrectSequence()
        {
            _manager.handleJavaServerReset(this);
        }

        //This method will handle any exception throwned inside any of the worker that is used
        public void handleWorkerException(Worker worker, Exception ex)
        {
            _log.Error("Worker type: " + worker.GetType().FullName + " throw exception:", ex);
            _log.Info("Reset client");
            _manager.handleJavaServerReset(this);
        }

        public void handleJavaServerReset()
        {
            _log.Error("Client connection dropped");
            _log.Info("Reset client");
            _manager.handleJavaServerReset(this);
        }
    }
}
